
/* GCSx
** LAYERLIST.CPP
**
** Listbox for layer selection
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

const string WLayerListBox::wtLayerVisible("o");
const string WLayerListBox::wtLayerEditable("p");
const string WLayerListBox::wtLayerSprites("s");
const string WLayerListBox::wtLayerTile("q");
const string WLayerListBox::wtLayerFont("r");
const string WLayerListBox::wtLayerImage("g");

WLayerListBox::WLayerListBox(int lId, int* lSelection, int lWidth, int lLines) : WListBox(lId, lSelection, 0, lWidth, lLines) { start_func
    // Determine sizing of icons, gutters and such
    int iconWidth[4];
    iconWidth[0] = fontWidth(wtLayerVisible, FONT_WIDGET);
    iconWidth[1] = fontWidth(wtLayerSprites, FONT_WIDGET);
    iconWidth[2] = fontWidth(wtLayerEditable, FONT_WIDGET);
    iconWidth[3] = max(fontWidth(wtLayerTile, FONT_WIDGET), fontWidth(wtLayerFont, FONT_WIDGET));
    iconWidth[3] = max(iconWidth[3], fontWidth(wtLayerImage, FONT_WIDGET));
    int iconGutter = iconWidth[0] / 2;
    
    int total = GUI_LISTBOX_HORIZPAD;
    for (int pos = 0; pos < 5; ++pos) {
        iconPos[pos] = total;
        if (pos < 4) total += iconWidth[pos] + iconGutter;
    }
    
    // Width of items is larger, for widgets
    showWidth += iconPos[4] - GUI_LISTBOX_HORIZPAD;
    
    // Height of items may need to be larger for widget
    valueHeight = max(valueHeight, fontHeight(FONT_WIDGET));
    
    // Center text and icons
    yAddIcon = yAddText = 0;
    int iconH = fontHeight(FONT_WIDGET);
    int textH = fontHeight();
    if (iconH > textH) yAddText = (iconH - textH) / 2;
    else yAddIcon = (textH - iconH) / 2;
    
    scene = NULL;
}

WLayerListBox::~WLayerListBox() { start_func
}

void WLayerListBox::setScene(SceneEdit* lScene) { start_func
    scene = lScene;
}

void WLayerListBox::removeItem(int position) { start_func
    // Remove item
    vector<ListEntry>::iterator pos = contents.begin();
    int posnum = 0;
    for (; (pos != contents.end()) && (posnum < position); ++pos, ++posnum) ;
    contents.erase(pos);
    --numValues;

    // Close up gap in ids
    for (int adjpos = position; adjpos < numValues; ++adjpos) {
        --contents[adjpos].id;
    }

    resize(valueWidth + GUI_LISTBOX_HORIZPAD + iconPos[3],
           valueHeight * numValues);
    
    // No items?
    if (!numValues) {
        dragSet = -1;
        cursorPos = 0;
        selectedItem = SELECTED_NONE;
    }
    else {
        // Adjust cursor/selected/drag?
        if (dragSet > posnum) --dragSet;
        else if (dragSet == posnum) dragSet = -1;
        if (selectedItem > posnum) --selectedItem;
        else if (selectedItem == posnum) selectedItem = -SELECTED_NONE;
        if (cursorPos > posnum) --cursorPos;
        else if (cursorPos == posnum) cursorPos = -1;
        if ((selectedItem == SELECTED_NONE) || (cursorPos < 0)) {
            // Move cursor to first non-disabled item
            int selpos = 0;
            for (; selpos < numValues; ++selpos) {
                if (!contents[selpos].disabled) break;
            }
            // (if nothing to select...)
            if (selpos >= numValues) selpos = 0;
            moveCursor(selpos);
            selectItem(selpos, 1);
        }
    }

    // Scroll, update screen
    if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
    setDirty();
}

void WLayerListBox::swapItems(int pos1, int pos2) { start_func
    // Swap cursor/selected/drag if applicable
    if (cursorPos == pos1) cursorPos = pos2;
    else if (cursorPos == pos2) cursorPos = pos1;
    if (selectedItem == pos1) selectedItem = pos2;
    else if (selectedItem == pos2) selectedItem = pos1;
    if (dragSet == pos1) dragSet = pos2;
    else if (dragSet == pos2) dragSet = pos1;
    
    // Swap the two items EXCEPT for ids (which are position based)
    swap(contents[pos1], contents[pos2]);
    swap(contents[pos1].id, contents[pos2].id);
    
    // Scroll, update screen
    if (wparent) wparent->scrollToView(0, cursorPos * valueHeight, width, valueHeight);
    setDirty();
}

void WLayerListBox::addItem(const ListEntry& item, int selectIt) { start_func
    addItem(numValues, item, selectIt);
}

void WLayerListBox::addItem(int position, const ListEntry& item, int selectIt) { start_func
    if (position >= numValues) {
        contents.push_back(item);
    }
    else {
        vector<ListEntry>::iterator pos = contents.begin();
        int posnum = 0;
        for (; (pos != contents.end()) && (posnum < position); ++pos, ++posnum) ;
        contents.insert(pos, item);
        position = posnum;

        // Modify ids (numValues hasn't changed yet)
        for (int adjpos = position + 1; adjpos <= numValues; ++adjpos) {
            ++contents[adjpos].id;
        }

        // Move cursor/selected/drag up if applicable
        if (cursorPos >= position) ++cursorPos;
        if (selectedItem >= position) ++selectedItem;
        if (dragSet >= position) ++dragSet;
    }

    numValues = contents.size();
    // (limit length of display but don't affect actual data)
    int itemWidth = fontWidth(item.label.substr(0, MAX_LINELENGTH));
    if (itemWidth > valueWidth) valueWidth = itemWidth;
    
    if (selectIt) {
        moveCursor(position);
        selectItem(cursorPos);
    }

    resize(valueWidth + GUI_LISTBOX_HORIZPAD + iconPos[3],
           valueHeight * numValues);
}

void WLayerListBox::display(SDL_Surface* destSurface, Rect& toDisplay, const Rect& clipArea, int xOffset, int yOffset) { start_func
    assert(destSurface);

    if (visible) {
        // If dirty, redraw all
        if (dirty) {
            getRect(toDisplay);
            toDisplay.x += xOffset;
            toDisplay.y += yOffset;
            // Always fill entire width of view
            if (viewWidth > toDisplay.w) toDisplay.w = viewWidth;
            dirty = 0;
            intersectRects(toDisplay, clipArea);
        }
        
        // Anything to draw?
        if (toDisplay.w) {
            SDL_SetClipRect(destSurface, &toDisplay);
        
            xOffset += x;
            yOffset += y;

            // This widget attempts to only redraw listbox entries within the
            // dirtied area.

            // Background
            SDL_FillRect(destSurface, &toDisplay, guiPacked[COLOR_TEXTBOX]);
            
            // Draw all listbox entries we can see
            int pos = (toDisplay.y - yOffset) / valueHeight;
            // (add in value height - 1 so it rounds up)
            int last = (toDisplay.y + toDisplay.h - yOffset + valueHeight - 1) / valueHeight;
            if (last > numValues) last = numValues;
            
            int shownWidth = valueWidth + iconPos[4] + GUI_LISTBOX_HORIZPAD;
            if (viewWidth > shownWidth) shownWidth = viewWidth;
            
            for (; pos < last; ++pos) {
                int color = COLOR_TEXT;
            
                if ((contents[pos].selected) && (!disabled) && (!contents[pos].disabled)) {
                    drawGradient(xOffset, valueHeight * pos + yOffset,
                             shownWidth, valueHeight, guiRGB[COLOR_SELECTION1],
                             guiRGB[COLOR_SELECTION2], destSurface);
                    drawText(contents[pos].label, guiRGB[COLOR_TEXTBOX],
                             xOffset + iconPos[4], valueHeight * pos + yOffset + yAddText, destSurface);
                    color = COLOR_TEXTBOX;
                }
                else {
                    if (disabled || contents[pos].disabled) color = COLOR_LIGHT2;
                    drawText(contents[pos].label, guiRGB[color],
                             xOffset + iconPos[4], valueHeight * pos + yOffset + yAddText, destSurface);
                }
                
                // Icons
                if (contents[pos].code1 & FLAG_VISIBLE) drawText(wtLayerVisible, guiRGB[contents[pos].code1 & FLAG_DIMMED ? COLOR_DARK2 : color == COLOR_LIGHT2 ? COLOR_TEXT : color], xOffset + iconPos[0], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
                if (contents[pos].code1 & FLAG_SPRITES_VISIBLE) drawText(wtLayerSprites, guiRGB[contents[pos].code1 & FLAG_SPRITES_DIMMED ? COLOR_DARK2 : color == COLOR_LIGHT2 ? COLOR_TEXT : color], xOffset + iconPos[1], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
                if (contents[pos].code1 & FLAG_EDITABLE) drawText(wtLayerEditable, guiRGB[color], xOffset + iconPos[2], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
                
                const string* icon = NULL;

                switch (contents[pos].code2) {
                    case Layer::LAYER_TILE:
                        icon = &wtLayerTile;
                        break;
                    case Layer::LAYER_IMAGE:
                        icon = &wtLayerImage;
                        break;
                    case Layer::LAYER_FONT:
                        icon = &wtLayerFont;
                        break;
                }

                if (icon) drawText(*icon, guiRGB[color], xOffset + iconPos[3], valueHeight * pos + yOffset + yAddIcon, destSurface, FONT_WIDGET);
            
                if ((haveFocus) && (pos == cursorPos)) {
                    drawFocusBox(xOffset, valueHeight * pos + yOffset,
                                 shownWidth, valueHeight, destSurface);
                }
            }
        }
    }
}

void WLayerListBox::dblClickItem(int id) { start_func
    // @TODO: technically should pass our overall frame parent to this function
    // but getting that info is a lot of work
    scene->getLayerEdit(id)->propertiesDialog(0);
}

void WLayerListBox::rtClickItem(int id) { start_func
    dblClickItem(id);
}

void WLayerListBox::delClickItem(int id) { start_func
    // @TODO: Confirmation if last non-disabled layer in list
    try {
        // @TODO: technically should pass our overall frame parent to this function
        // but getting that info is a lot of work
        scene->deleteLayer(id);
    }
    catch (FileException& e) {
        guiErrorBox(string(e.details), errorTitleFile);
    }
}

int WLayerListBox::event(int hasFocus, const SDL_Event* event) { start_func
    assert(event);
    assert(parent);
    
    if ((numValues == 0) && (event->type != SDL_INPUTFOCUS)) return 0;

    switch (event->type) {
        case SDL_KEYDOWN:
            switch (combineKey(event->key.keysym.sym, event->key.keysym.mod)) {
                case SDLK_KP_ENTER:
                case SDLK_RETURN:
                case SDLK_SPACE:
                    dblClickItem(cursorPos);
                    return 1;

                case SDLK_DELETE:
                case SDLK_BACKSPACE:
                    delClickItem(cursorPos);
                    return 1;
            }
            break;

        case SDL_COMMAND:
            switch (event->user.code) {
                case VIEW_ALLLAYER:
                case VIEW_DIMLAYER:
                case VIEW_NOLAYER:
                    // Change all non-selected entries
                    for (int pos = 0; pos < numValues; ++pos) {
                        if (!contents[pos].selected) {
                            contents[pos].code1 = contents[pos].code1 & FLAG_EDITABLE;
                            if (event->user.code != VIEW_NOLAYER) contents[pos].code1 = contents[pos].code1 | FLAG_VISIBLE | FLAG_SPRITES_VISIBLE;
                            if (event->user.code == VIEW_DIMLAYER) contents[pos].code1 = contents[pos].code1 | FLAG_DIMMED | FLAG_SPRITES_DIMMED;
                        }
                    }
                    if (wparent) wparent->myParent()->childModified((WListBox*)this);
                    setDirty();
                    return 1;

                case VIEW_NEXT:
                    if (selectedItem >= 0) {
                        for (int pos = selectedItem + 1; pos < numValues; ++pos) {
                            if (!contents[pos].disabled) {
                                selectItem(pos, 1, 1);
                                break;
                            }
                        }
                    }
                    return 1;

                case VIEW_PREV:
                    if (selectedItem >= 0) {
                        for (int pos = selectedItem - 1; pos >= 0; --pos) {
                            if (!contents[pos].disabled) {
                                selectItem(pos, 1, 1);
                                break;
                            }
                        }
                    }
                    return 1;
            }
            break;
    
        case SDL_MOUSEMOTION:
            // Dragging lets us move layers
            if (event->motion.state & SDL_BUTTON_LMASK) {
                int dragToPos = event->motion.y / valueHeight;
                
                // If dragging to another position, and something IS being dragged...
                if ((event->motion.y < height) && (dragToPos != dragSet) && (dragSet >= 0)) {
                    try {
                        // @TODO: technically should pass our overall frame parent to this function
                        // but getting that info is a lot of work
                        if (scene) scene->swapLayer(dragSet, dragToPos);
                    }
                    catch (UndoException &e) {
                    }
                }
                
                return 1;
            }
            break;

        case SDL_MOUSEBUTTONDBL:
        case SDL_MOUSEBUTTONDOWN:
            // Right click- properties @TODO: context-menu
            if (event->button.button == SDL_BUTTON_RIGHT) {
                if (event->button.y < height) {
                    // Move cursor, don't select
                    moveCursor(event->motion.y / valueHeight);
                    rtClickItem(cursorPos);
                    return 1;
                }
            }
        
            if (event->button.button == SDL_BUTTON_LEFT) {
                int targetPos = event->button.y / valueHeight;

                // (nothing being dragged, by default)
                dragSet = -1;

                if (event->button.y < height) {
                    if (event->button.x < iconPos[4]) {
                        // (disallow invisible/dim/uneditable active layer(s))
                        if (!contents[targetPos].selected) {
                            // Which icon was clicked?
                            if (event->button.x < iconPos[1]) {
                                if (contents[targetPos].code1 & FLAG_DIMMED) contents[targetPos].code1 &= ~(FLAG_VISIBLE | FLAG_DIMMED);
                                else if (contents[targetPos].code1 & FLAG_VISIBLE) contents[targetPos].code1 |= FLAG_DIMMED;
                                else contents[targetPos].code1 |= FLAG_VISIBLE;
                                // (make uneditable if invisible, also)
                                if (!(contents[targetPos].code1 & FLAG_VISIBLE)) contents[targetPos].code1 &= ~FLAG_EDITABLE;
                            }
        
                            // Disallow editable disabled/invisible layers
                            if ((event->button.x >= iconPos[2]) && (event->button.x < iconPos[3]) && (!contents[targetPos].disabled) && (contents[targetPos].code1 & FLAG_VISIBLE)) {
                                contents[targetPos].code1 ^= FLAG_EDITABLE;
                            }
                        }
                        
                        // (sprite visibility currently unaffected by other things)
                        if ((event->button.x >= iconPos[1]) && (event->button.x < iconPos[2])) {
                            if (contents[targetPos].code1 & FLAG_SPRITES_DIMMED) contents[targetPos].code1 &= ~(FLAG_SPRITES_VISIBLE | FLAG_SPRITES_DIMMED);
                            else if (contents[targetPos].code1 & FLAG_SPRITES_VISIBLE) contents[targetPos].code1 |= FLAG_SPRITES_DIMMED;
                            else contents[targetPos].code1 |= FLAG_SPRITES_VISIBLE;
                        }
                        
                        if (wparent) wparent->myParent()->childModified((WListBox*)this);
                        setDirty();
                    }
                    else {
                        // Select item, allow dragging or handle dbl-click
                        moveCursor(targetPos);
                        selectItem(cursorPos, 1);
                        
                        if (event->type == SDL_MOUSEBUTTONDBL) dblClickItem(cursorPos);
                        else dragSet = cursorPos;
                    }
                }
                    
                return 1;
            }
            break;
    }

    return WListBox::event(hasFocus, event);
}

void WLayerListBox::selectItem(int id, int select, int alone) { start_func
    assert(id >= 0);
    assert(id < numValues);
    
    // (if disabled, no effect at all)
    if (contents[id].disabled) return;
    
    // We assume 'alone' and 'select' are both true
    
    // (manually deselect all so we don't send dirty/modified yet)
    for (int pos = 0; pos < numValues; ++pos) {
        if (contents[pos].selected) {
            // Change the item we're deselecting to match the item we're selecting
            // (visible, invisible, dim; editable)
            contents[pos].code1 = contents[id].code1;
            contents[pos].selected = 0;
        }
    }
    
    selectedItem = id;
    contents[id].selected = 1;
    // Item we're selecting is 100% visible and editable, now
    contents[id].code1 = FLAG_VISIBLE | FLAG_EDITABLE | FLAG_SPRITES_VISIBLE;

    if (wparent) wparent->myParent()->childModified(this);
    setDirty();
}

void WLayerListBox::setBitMask(Uint32 layersAffect, Uint32 layersView, Uint32 layersDim, int selectedLayer) { start_func
    int dirty = 0;
    for (int pos = 0; pos < numValues; ++pos) {
        int bit = 1 << pos;
        int viewType = 0;
        if (layersView & bit) {
            if (layersDim & bit) viewType = FLAG_VISIBLE | FLAG_DIMMED;
            else viewType = FLAG_VISIBLE;
        }
        if ((contents[pos].code1 & (FLAG_VISIBLE | FLAG_DIMMED)) != viewType) {
            contents[pos].code1 = (contents[pos].code1 & ~(FLAG_VISIBLE | FLAG_DIMMED)) | viewType;
            dirty = 1;
        }
        if (layersAffect & bit) {
            if (!(contents[pos].code1 & FLAG_EDITABLE)) {
                contents[pos].code1 |= FLAG_EDITABLE;
                dirty = 1;
            }
        }
        else if (contents[pos].code1 & FLAG_EDITABLE) {
            contents[pos].code1 &= ~FLAG_EDITABLE;
            dirty = 1;
        }
        // Selection modification?
        if (selectedLayer >= 0) {
            if (selectedLayer == pos) {
                if (!contents[pos].selected) {
                    contents[pos].selected = 1;
                    selectedItem = pos;
                    dirty = 1;
                }
            }
            else if (contents[pos].selected) {
                contents[pos].selected = 0;
                dirty = 1;
            }
        }
    }
    if (dirty) setDirty();
}

void WLayerListBox::determineBitMasks(Uint32& layersView, Uint32& layersDim, Uint32& layersAffect) const { start_func
    layersView = 0;
    layersDim = 0;
    layersAffect = 0;
    
    for (int pos = 0; pos < numValues; ++pos) {
        int bit = 1 << pos;
        if (contents[pos].code1 & FLAG_VISIBLE) {
            layersView |= bit;
            if (contents[pos].code1 & FLAG_DIMMED) layersDim |= bit;
        }
        if (contents[pos].code1 & FLAG_EDITABLE) layersAffect |= bit;
    }
}


